#include <QJsonObject>
#include <QJsonArray>

#include <osg/Geometry>
#include<gp_Circ.hxx>
#include<GC_MakeArcOfCircle.hxx>
#include<GC_MakeCircle.hxx>

#include "Arc3D.h"
#include "JsonReader.h"
#include "GLSLPrograms.h"
#include "Arc2.h"
#include "Ucs.h"
#include "Arc2D.h"
#include "OcctOsg.h"
#include "OsgUtil.h"
#include "GetTriangles.h"

void getProjPointOnPlane(const Plane& plane, const osg::Vec3d& worldpt, osg::Vec3d& planePt);

struct Arc3DPrivate
{
	Arc3DPrivate()
		: m_rep3D(new osg::Geometry())
	{
		m_rep3D->setNodeMask(D3CurveMask);
		osg::ref_ptr<osg::Program> program = createEdgeProgram();
		auto ss = m_rep3D->getOrCreateStateSet();
		ss->setAttributeAndModes(program, osg::StateAttribute::ON);
	}
	osg::ref_ptr<osg::Geometry> m_rep3D;
	osg::Vec4 m_color = { 1, 1, 1, 1 };
	bool m_selected = false;
	osg::Vec3d m_center;
	double m_radius = 0;
	float m_startAngle = 0;
	float m_endAngle = 0;
	mutable std::vector<osg::Vec3d> m_handles;
};

Arc3D::Arc3D(INode* parent)
	: Ent3DCurve(parent)
	, d_ptr(new Arc3DPrivate)
{
}

std::shared_ptr<Ent> Arc3D::clone() const
{
	auto arc = std::make_shared<Arc3D>(getParent());
	arc->copy(*this);
	return arc;
}
extern bool isCircle(double startAngle, double endAngle);

void Arc3D::set(const osg::Vec3d& center, double radius, float startAngle, float endAngle)
{
	d_ptr->m_center = center;
	d_ptr->m_radius = radius;
	if (startAngle > endAngle)
		d_ptr->m_startAngle = startAngle - 2 * M_PI;
	else
		d_ptr->m_startAngle = startAngle;
	d_ptr->m_endAngle = endAngle;
	normalize();
	addFlags(UPDATE_GEOMETRY);
}

void Arc3D::setCircle(const osg::Vec3d & center, const double radius)
{
	set(center, radius, 0, 2.0 * M_PI);
}

bool Arc3D::setCircle3P(const osg::Vec3d & p0, const osg::Vec3d & p1, const osg::Vec3d & p2)
{
	Plane  plane;
	plane.setOrigin(p1);
	bool success = plane.setAxis(p0 - p1, p2 - p1);
	if (!success)
		return false;
	osg::Vec3d planePt1, planePt3;
	getProjPointOnPlane(plane, p0, planePt1);
	getProjPointOnPlane(plane, p2, planePt3);
	auto m = plane.getMatrix();
	m = osg::Matrixd::inverse(m);
	planePt1 = planePt1 * m;
	planePt3 = planePt3 * m;
	CArc2 arc(
		CDblPoint(planePt1.x(), planePt1.y()),
		CDblPoint(),
		CDblPoint(planePt3.x(), planePt3.y())
	);
	setOrigin(plane.origin());
	setAxis(plane.xAxis(), plane.yAxis());
	set(osg::Vec3d(arc.center.x, arc.center.y, 0), arc.radius, 0, 2*M_PI);
	normalize();
	return true;
}

bool Arc3D::set3P(const osg::Vec3d & p0, const osg::Vec3d & p1, const osg::Vec3d & p2)
{
	Plane  plane;
	plane.setOrigin(p1);
	bool success = plane.setAxis(p0 - p1, p2 - p1);
	if (!success)
		return false;
	osg::Vec3d planePt1, planePt3;
	getProjPointOnPlane(plane, p0, planePt1);
	getProjPointOnPlane(plane, p2, planePt3);
	auto m = plane.getMatrix();
	m = osg::Matrixd::inverse(m);
	planePt1 = planePt1 * m;
	planePt3 = planePt3 * m;
	CArc2 arc(
		CDblPoint(planePt1.x(),planePt1.y()),
		CDblPoint(),
		CDblPoint(planePt3.x(),planePt3.y())
	);
	setOrigin(plane.origin());
	setAxis(plane.xAxis(), plane.yAxis());
	set(osg::Vec3d(arc.center.x, arc.center.y, 0), arc.radius, arc.StartAngle(), arc.EndAngle());
	normalize();
	return true;
}

bool Arc3D::setCSE(const osg::Vec3d & center, const osg::Vec3d & start, const osg::Vec3d & end)
{
	Plane  plane;
	plane.setOrigin(center);
	bool success = plane.setAxis(start - center, end - center);
	if (!success)
		return false;
	osg::Vec3d planeStart, planeEnd;
	getProjPointOnPlane(plane, start, planeStart);
	getProjPointOnPlane(plane, end, planeEnd);
	auto m = plane.getMatrix();
	m = osg::Matrixd::inverse(m);
	planeStart = planeStart * m;
	planeEnd = planeEnd * m;
	CArc2 arc({ planeStart.x(),planeStart.y() }, { 0, 0 }, { planeEnd.x(), planeEnd.y() }, 0);
	setOrigin(center);
	setAxis(plane.xAxis(), plane.yAxis());
	set(osg::Vec3d(), arc.radius, arc.StartAngle(), arc.EndAngle());
	normalize();
	return true;
}

bool Arc3D::setCircleCSE(const osg::Vec3d & center, const osg::Vec3d & start, const osg::Vec3d & end)
{
	Plane  plane;
	plane.setOrigin(center);
	bool success = plane.setAxis(start - center, end - center);
	if (!success)
		return false;
	osg::Vec3d planeStart, planeEnd;
	getProjPointOnPlane(plane, start, planeStart);
	getProjPointOnPlane(plane, end, planeEnd);
	auto m = plane.getMatrix();
	m = osg::Matrixd::inverse(m);
	planeStart = planeStart * m;
	planeEnd = planeEnd * m;
	CArc2 arc({ planeStart.x(),planeStart.y() }, { 0, 0 }, { planeEnd.x(), planeEnd.y() }, 0);
	setOrigin(center);
	setAxis(plane.xAxis(), plane.yAxis());
	set(osg::Vec3d(), arc.radius, 0, 2*M_PI);
	normalize();
	return true;
}

void Arc3D::setColor(const osg::Vec4 &color)
{
	if (d_ptr->m_color != color)
	{
		d_ptr->m_color = color;
		addFlags(UPDATE_GEOMETRY);
	}
}

void Arc3D::normalize()
{
	auto m = getMatrix();
	m.transpose3x3(m);
	auto center = d_ptr->m_center + osg::Matrixd::transform3x3(origin(), m);
	d_ptr->m_center = center;
	setOrigin(osg::Vec3d());
}

const osg::Vec4& Arc3D::color() const
{
	return d_ptr->m_color;
}

const osg::Vec3d& Arc3D::center() const
{
	return d_ptr->m_center;
}

double Arc3D::radius() const
{
	return d_ptr->m_radius;
}

float Arc3D::startAngle() const
{
	return d_ptr->m_startAngle;
}

float Arc3D::endAngle() const
{
	return d_ptr->m_endAngle;
}

void Arc3D::update3D()
{
	if (flags())
	{
		const osg::Vec4f& selectedColor = osg::Vec4f(0.f, 0.8f, 0.f, 1.f);
		auto va = new osg::Vec3Array();
		auto points = getArcPoints(d_ptr->m_startAngle, d_ptr->m_endAngle, d_ptr->m_radius, d_ptr->m_center, osg::Vec3d(0, 0, 1), 3);
		appendPoints(va, points);

		for (int i = 0;i< va->size();i++)
		{
			auto m = getMatrix();
			(*va)[i] = (*va)[i] * m;
		}
		
		d_ptr->m_rep3D->setVertexArray(va);

		osg::ref_ptr<osg::Vec4Array> ca = new osg::Vec4Array();
		
		if (d_ptr->m_selected)
			ca->push_back(selectedColor);
		else
			ca->push_back(d_ptr->m_color);
		
		d_ptr->m_rep3D->setColorArray(ca.get(), osg::Array::BIND_OVERALL);

		d_ptr->m_rep3D->getPrimitiveSetList().clear();
		d_ptr->m_rep3D->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::LINE_STRIP, 0, va->size()));

		d_ptr->m_rep3D->dirtyGLObjects();

		calHandles();
		removeFlags(0xFFFFFFFF);
	}
}

osg::Node * Arc3D::getRep3D() const
{
	return d_ptr->m_rep3D.get();
}

void Arc3D::setSelected(bool val)
{
	if (d_ptr->m_selected != val)
	{
		d_ptr->m_selected = val;
		addFlags(UPDATE_GEOMETRY);
	}
}

void Arc3D::save(QJsonObject & json) const
{
	Ent3DCurve::save(json);
	Plane::save(json);

	json["radius"] = d_ptr->m_radius;
	json["startAngle"] = d_ptr->m_startAngle;
	json["endAngle"] = d_ptr->m_endAngle;
	json["center"] = saveVec3f(d_ptr->m_center);
	json["color"] = saveVec4f(d_ptr->m_color);
}

void Arc3D::load(const QJsonObject& json, JsonReader& reader)
{
	Ent3DCurve::load(json, reader);
	Plane::load(json);

	d_ptr->m_radius = json["radius"].toDouble();
	d_ptr->m_startAngle = json["startAngle"].toDouble();
	d_ptr->m_endAngle = json["endAngle"].toDouble();
	d_ptr->m_center = loadVec3f(json["center"].toArray());
	d_ptr->m_color = loadVec4f(json["color"].toArray());
}

void Arc3D::calHandles() const
{
	d_ptr->m_handles.clear();
	auto m = getMatrix();
	if (isCircle(d_ptr->m_startAngle,d_ptr->m_endAngle))
	{
		d_ptr->m_handles.push_back((d_ptr->m_center + osg::Vec3d(d_ptr->m_radius, 0, 0)) * m);
		d_ptr->m_handles.push_back((d_ptr->m_center + osg::Vec3d(0, d_ptr->m_radius, 0)) * m);
		d_ptr->m_handles.push_back((d_ptr->m_center - osg::Vec3d(d_ptr->m_radius, 0, 0)) * m);
		d_ptr->m_handles.push_back((d_ptr->m_center - osg::Vec3d(0, d_ptr->m_radius, 0)) * m);
		d_ptr->m_handles.push_back(d_ptr->m_center * m);
	}
	else
	{
		const double midAngle = (d_ptr->m_startAngle + d_ptr->m_endAngle) / 2;
		osg::Vec3d startPt = getPointOfAngle(d_ptr->m_startAngle, d_ptr->m_center, d_ptr->m_radius);
		osg::Vec3d midPt = getPointOfAngle(midAngle, d_ptr->m_center, d_ptr->m_radius);
		osg::Vec3d endPt = getPointOfAngle(d_ptr->m_endAngle, d_ptr->m_center, d_ptr->m_radius);

		startPt = startPt * m;
		midPt = midPt * m;
		endPt = endPt * m;

		d_ptr->m_handles.push_back(startPt);
		d_ptr->m_handles.push_back(midPt);
		d_ptr->m_handles.push_back(endPt);
	}
	
}

std::vector<osg::Vec3d> Arc3D::handles() const
{
	if (flags())
	{
		calHandles();
	}
	return d_ptr->m_handles;
}

void Arc3D::move(const osg::Vec3d& offset)
{
	setOrigin(origin() + offset);
	addFlags(UPDATE_GEOMETRY);
}

void Arc3D::scale(const osg::Vec3d & center, double scale)
{
	d_ptr->m_center = center + (d_ptr->m_center - center) * scale;
	d_ptr->m_radius = d_ptr->m_radius * scale;
}

void Arc3D::stretch(int index, const osg::Vec3d &offset)
{
	if (d_ptr->m_handles.empty())
		calHandles();
	d_ptr->m_handles[index] = d_ptr->m_handles[index] + offset;
	const osg::Vec3d startPt = d_ptr->m_handles[0];
	const osg::Vec3d secondPt = d_ptr->m_handles[1];
	const osg::Vec3d endPt = d_ptr->m_handles[2];
	bool suc = set3P(startPt, secondPt, endPt);
	if(suc)
		addFlags(UPDATE_GEOMETRY);
}

void Arc3D::copy(const Ent& ent)
{
	auto arc = dynamic_cast<const Arc3D*>(&ent);
	if (arc)
	{
		setOrigin(arc->origin());
		setAxis(arc->xAxis(), arc->yAxis());
		d_ptr->m_color = arc->color();
		d_ptr->m_startAngle = arc->startAngle();
		d_ptr->m_endAngle = arc->endAngle();
		d_ptr->m_center = arc->center();
		d_ptr->m_radius = arc->radius();
		d_ptr->m_handles = arc->handles();
		addFlags(UPDATE_GEOMETRY);
	}
}

std::vector<Handle(Geom_Curve)> Arc3D::getCurve3d() const
{
	std::vector<Handle(Geom_Curve)> result;
	if (d_ptr->m_endAngle > d_ptr->m_startAngle)
	{
		if (std::abs(d_ptr->m_endAngle - d_ptr->m_startAngle - M_PI * 2) < 1e-5)
		{
			GC_MakeCircle maker(toPnt(d_ptr->m_handles[0]), toPnt(d_ptr->m_handles[1]), toPnt(d_ptr->m_handles[2]));
			if (maker.IsDone())
			{
				Handle(Geom_Curve) curve = maker.Value();
				result.push_back(new Geom_TrimmedCurve(curve, curve->FirstParameter(), curve->LastParameter()));
			}
		}
		else
		{
			GC_MakeArcOfCircle maker(toPnt(d_ptr->m_handles[0]), toPnt(d_ptr->m_handles[1]), toPnt(d_ptr->m_handles[2]));
			if (maker.IsDone())
				result.push_back(maker.Value());
		}
	}
	return result;

}

void Arc3D::rotate(const double angle, const osg::Vec2d & basePoint)
{
	auto m = getMatrix();
	auto r = rotate2DMatrix(basePoint.x(), basePoint.y(), angle);
	auto m2 = m * r;
	setOrigin(osg::Vec3d(0,0,0) * m2);
	osg::Vec3d xDir = osg::Matrixd::transform3x3(osg::Vec3d(1, 0, 0), m2);
	osg::Vec3d yDir = osg::Matrixd::transform3x3(osg::Vec3d(0, 1, 0), m2);
	setAxis(xDir, yDir);
}
